package com.michaelbulava.TFS.vcs.internal.providers;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.committed.DecoratorManager;
import com.intellij.openapi.vcs.changes.committed.VcsCommittedListsZipper;
import com.intellij.openapi.vcs.changes.committed.VcsCommittedViewAuxiliary;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
import com.intellij.openapi.vcs.versionBrowser.ChangesBrowserSettingsEditor;
import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.AsynchConsumer;
import com.michaelbulava.TFS.Messages;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.vcs.TFSVcs;
import com.michaelbulava.TFS.vcs.exceptions.NotConnectedToTFS;
import com.michaelbulava.TFS.vcs.exceptions.TFSException;
import com.michaelbulava.TFS.vcs.exceptions.UnableToCreateTFSFilePathException;
import com.michaelbulava.TFS.vcs.internal.ServerStatus;
import com.michaelbulava.TFS.vcs.internal.TFSFiles.TFSFilePath;
import com.michaelbulava.TFS.vcs.internal.TFSRepositoryLocation;
import com.michaelbulava.TFS.vcs.internal.WorkstationHelper;
import com.michaelbulava.TFS.vcs.internal.providers.internal.TFSChangeList;
import com.michaelbulava.TFS.vcs.ui.TFSVersionFilterComponent;
import com.michaelbulava.TFS.vcs.utils.TFSFileUtil;
import com.microsoft.tfs.core.clients.versioncontrol.GetItemsOptions;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.*;
import com.microsoft.tfs.core.clients.versioncontrol.specs.ItemSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.ChangesetVersionSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.DateVersionSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.LatestVersionSpec;
import com.microsoft.tfs.core.clients.versioncontrol.specs.version.VersionSpec;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;

/**
 * Created by MichaelA on 12/21/2015.
 */
public class TFSCommitedChangesProvider2 implements CachingCommittedChangesProvider<TFSChangeList, ChangeBrowserSettings> {
    private static final Logger log = FileLogger.getInstance(TFSCommitedChangesProvider2.class);
    private final TFSVcs myVcs;
    public TFSCommitedChangesProvider2(TFSVcs vcs){
        this.myVcs = vcs;
    }
    /**
     * Returns the current version of the binary data format that is read/written by the caching provider.
     * If the format version loaded from the cache stream does not match the format version returned by
     * the provider, the cache stream is discarded and changes are reloaded from server.
     *
     * @return binary format version.
     */
    @Override
    public int getFormatVersion() {
        return 3;
    }

    @Override
    public void writeChangeList(DataOutput stream, TFSChangeList list) throws IOException {
        list.writeToStream(stream);
    }

    @Override
    public TFSChangeList readChangeList(RepositoryLocation location, DataInput stream) throws IOException {
        return new TFSChangeList(myVcs, stream);
    }

    /**
     * Returns true if the underlying VCS allows to limit the number of loaded changes. If the VCS does not
     * support that, filtering by date will be used when initializing history cache.
     *
     * @return true if number limit is supported, false otherwise.
     */
    @Override
    public boolean isMaxCountSupported() {
        return true;
    }

    /**
     * Returns the list of files under the specified repository root which may contain incoming changes.
     * This method is an optional optimization: if null is returned, all files are checked through DiffProvider
     * in a regular way.
     *
     * @param location the location where changes are requested.
     * @return the files which may contain the changes, or null if the call is not supported.
     */
    @Nullable
    @Override
    public Collection<FilePath> getIncomingFiles(RepositoryLocation location) throws VcsException {
        if (location instanceof TFSRepositoryLocation){
            TFSRepositoryLocation repositoryLocation = (TFSRepositoryLocation)location;
            Collection<FilePath> result = new ArrayList<FilePath>();
            for(Map.Entry<Workspace, List<FilePath>> entry : repositoryLocation.getPathsByWorkspaces().entrySet()){
                Workspace workspace = entry.getKey();
                List<FilePath> files = entry.getValue();
                ItemSpec[] itemSpecs = TFSFileUtil.getItemSpecs(myVcs.getProject(), workspace, files, true, true);
                ExtendedItem[][] extendedItems = workspace.getExtendedItems(itemSpecs, DeletedState.ANY, ItemType.ANY);
                for(int i = 0; i < extendedItems.length; i++){
                    for(int j = 0; j < extendedItems[i].length; j++){
                        ExtendedItem item = extendedItems[i][j];
                        if (item.getLocalVersion() < item.getLatestVersion())
                            result.add(new TFSFilePath(workspace, item));
                    }
                }
//                for(FilePath file : files){
//                    if (!(file instanceof TFSFilePath)){
//                        try {
//                            file = new TFSFilePath(workspace, file);
//                        } catch (UnableToCreateTFSFilePathException e) {
//                            log.info(e.getMessage());
//                            continue;
//                        }
//                    }
//                    TFSFilePath tfsFile = (TFSFilePath)file;
//                    if (tfsFile.getLocalVersion() < tfsFile.getLatestVersion())
//                        result.add(tfsFile);
//                }
            }
            return result;
        }
        return null;
    }

    /**
     * Returns true if the changelist number restriction should be used when refreshing the cache,
     * or false if the date restriction should be used.
     *
     * @return true if restrict by number, false if restrict by date
     */
    @Override
    public boolean refreshCacheByNumber() {
        return false;
    }

    /**
     * Returns the name of the "changelist" concept in the specified VCS (changelist, revision etc.)
     *
     * @return the name of the concept, or null if the VCS (like CVS) does not use changelist numbering.
     */
    @Nls
    @Nullable
    @Override
    public String getChangelistTitle() {
        return "Change Set";
    }

    @Override
    public boolean isChangeLocallyAvailable(FilePath filePath, @Nullable VcsRevisionNumber localRevision, VcsRevisionNumber changeRevision, TFSChangeList changeList) {
        return false;
    }

    /**
     * Returns true if a timer-based refresh of committed changes should be followed by refresh of incoming changes, so that,
     * for example, changes from the wrong branch would be automatically removed from view.
     *
     * @return true if auto-refresh includes incoming changes refresh, false otherwise
     */
    @Override
    public boolean refreshIncomingWithCommitted() {
        return true;
    }

    @NotNull
    @Override
    public ChangeBrowserSettings createDefaultSettings() {
        return new ChangeBrowserSettings();
    }

    @Override
    public ChangesBrowserSettingsEditor<ChangeBrowserSettings> createFilterUI(boolean showDateFilter) {
        return new TFSVersionFilterComponent(showDateFilter);
    }

    @Nullable
    @Override
    public RepositoryLocation getLocationFor(FilePath root) {
        final Map<Workspace, List<FilePath>> pathsByWorkspaces = new HashMap<>();
        try{
            WorkstationHelper.processByWorkspaces(Collections.singletonList(root), true, myVcs.getProject(),
                    new WorkstationHelper.VoidDelegateProcess() {
                        @Override
                        public void executeRequest(Workspace workspace, List<FilePath> localPaths) throws TFSException {
                            pathsByWorkspaces.put(workspace, localPaths);
                        }
                    });
            if (!pathsByWorkspaces.isEmpty())
                return new TFSRepositoryLocation(pathsByWorkspaces);
        } catch (NotConnectedToTFS e){
            return null;
        } catch (TFSException e) {
            AbstractVcsHelper.getInstance(myVcs.getProject()).showError(new VcsException(e.getMessage(), e), TFSVcs.VCS_NAME);
        } catch(ProcessCanceledException e){
            AbstractVcsHelper.getInstance(myVcs.getProject()).showError(new VcsException(Messages
                    .getString("TFSCommittedChangesProvider.CanceledByUser"), e),TFSVcs.VCS_NAME);
        }
        return null;
    }

    @Nullable
    @Override
    public RepositoryLocation getLocationFor(FilePath root, String repositoryPath) {
        return getLocationFor(root);
    }

    @Nullable
    @Override
    public VcsCommittedListsZipper getZipper() {
        return null;
    }

    @Override
    public List<TFSChangeList> getCommittedChanges(ChangeBrowserSettings settings, RepositoryLocation location, int maxCount) throws VcsException {
        final List<TFSChangeList> result = new ArrayList<>();
        loadCommittedChanges(settings, location, maxCount, new AsynchConsumer<CommittedChangeList>() {
            @Override
            public void finished() {}

            @Override
            public void consume(CommittedChangeList committedChangeList) {
                result.add((TFSChangeList) committedChangeList);
            }
        });
        return result;
    }


    @Override
    public void loadCommittedChanges(ChangeBrowserSettings settings, RepositoryLocation location, int maxCount, AsynchConsumer<CommittedChangeList> consumer) throws VcsException {
        if (maxCount == 0)
            maxCount = Integer.MAX_VALUE;

        VersionSpec versionFrom = new ChangesetVersionSpec(1);
        if (settings.getChangeAfterFilter() != null)
            versionFrom = new ChangesetVersionSpec(settings.getChangeAfterFilter().intValue());
        if (settings.getDateAfterFilter() != null){
            Calendar c = Calendar.getInstance();
            c.setTime(settings.getDateAfterFilter());
            versionFrom = new DateVersionSpec(c);
        }
        VersionSpec versionTo = LatestVersionSpec.INSTANCE;
        if (settings.getChangeBeforeFilter() != null)
            versionTo = new ChangesetVersionSpec(settings.getChangeBeforeFilter().intValue());
        if (settings.getDateBeforeFilter() != null){
            Calendar c = Calendar.getInstance();
            c.setTime(settings.getDateBeforeFilter());
            versionTo = new DateVersionSpec(c);
        }

        try{
            TFSRepositoryLocation tfsRepositoryLocation = (TFSRepositoryLocation) location;
            for (Map.Entry<Workspace, List<FilePath>> entry : tfsRepositoryLocation.getPathsByWorkspaces().entrySet()){
                Workspace workspace = entry.getKey();
                List<FilePath> mappedPaths = entry.getValue();
                for(FilePath mapped : mappedPaths){
                    WorkingFolder wf = workspace.getClosestMappingForLocalPath(mapped.getPath()+"/*");
                    //TODO Look at reversing the directionality of the result set...
                    Changeset[] changesets = workspace.queryHistory(wf.getServerItem(),
                            versionTo, -1, RecursionType.FULL,
                            null, versionFrom, versionTo, maxCount, false, true, true, true);
                    for(Changeset change : changesets){
                        TFSChangeList newList = new TFSChangeList(workspace, change.getChangesetID(),
                                change.getCommitterDisplayName(), change.getDate().getTime(),
                                change.getComment(), myVcs);
                        consumer.consume(newList);
                    }
                }
            }
        } catch (Throwable tr){
            tr.printStackTrace();
        } finally {
            consumer.finished();;
        }
    }

    @Override
    public ChangeListColumn[] getColumns() {
        return new ChangeListColumn[] {
                new ChangeListColumn.ChangeListNumberColumn(Messages.getString("TFSCommittedChangesProvider.RevisionColumn")),
                ChangeListColumn.NAME,
                ChangeListColumn.DATE,
                ChangeListColumn.DESCRIPTION
        };
    }

    @Nullable
    @Override
    public VcsCommittedViewAuxiliary createActions(DecoratorManager manager, RepositoryLocation location) {
        return null;
    }

    /**
     * since may be different for different VCSs
     */
    @Override
    public int getUnlimitedCountValue() {
        return 0;
    }

    /**
     * @param file
     * @param number
     * @return required list and path of the target file in that revision (changes when move/rename)
     */
    @Nullable
    @Override
    public Pair<TFSChangeList, FilePath> getOneList(VirtualFile file, VcsRevisionNumber number) throws VcsException {
        return null;
    }

    @Override
    public RepositoryLocation getForNonLocal(VirtualFile file) {
        return null;
    }

    /**
     * Return true if this committed changes provider can be used to show the incoming changes.
     * If false is returned, the "Incoming" tab won't be shown in the Changes toolwindow.
     */
    @Override
    public boolean supportsIncomingChanges() {
        return true;
    }
}
